import { makeHttpRequestV2 } from "@app/containers/api/api.common";
import { ResponseMessage, ResponseStatusCode } from "@sparrow/common/enums";
import { environmentType } from "@sparrow/common/enums";
import {
  createDeepCopy,
  InitRequestTab,
  InitTestflowScheduleTab,
  scrollToTab,
  InitEnvironmentTab,
  InitTestflowDataSetTab,
} from "@sparrow/common/utils";
import { RequestTabAdapter, TestflowTabAdapter } from "../../../../adapter";
import type {
  EnvironmentDocument,
  TabDocument,
} from "../../../../database/database";
import { CollectionRepository } from "../../../../repositories/collection.repository";
import { EnvironmentRepository } from "../../../../repositories/environment.repository";
import { GuestUserRepository } from "../../../../repositories/guest-user.repository";
import { TabRepository } from "../../../../repositories/tab.repository";
import { TestflowRepository } from "../../../../repositories/testflow.repository";
import { WorkspaceRepository } from "../../../../repositories/workspace.repository";
import { TestflowService } from "../../../../services/testflow.service";
import {
  RequestDataTypeEnum,
  TestCaseConditionOperatorEnum,
  TestCaseSelectionTypeEnum,
  type HttpRequestCollectionLevelAuthTabInterface,
  type Tests,
} from "@sparrow/common/types/workspace";
import {
  TabPersistenceTypeEnum,
  type Tab,
} from "@sparrow/common/types/workspace/tab";
import type {
  ENVDocumentType,
  ENVExtractVariableType,
} from "@sparrow/common/types/workspace/environment";

import type {
  TFAPIResponseType,
  TFDataStoreType,
  TFEdgeType,
  TFHistoryAPIResponseStoreType,
  TFHistoryStoreType,
  TFKeyValueStoreType,
  TFNodeStoreType,
  TFNodeType,
} from "@sparrow/common/types/workspace/testflow";
import { CompareArray, Debounce, ParseTime } from "@sparrow/common/utils";
import { notifications } from "@sparrow/library/ui";
import { DecodeTestflow } from "@sparrow/workspaces/features/testflow-explorer/utils";
import { testFlowDataStore } from "@sparrow/workspaces/features/testflow-explorer/store";
import { BehaviorSubject, Observable } from "rxjs";
import {
  CollectionAuthTypeBaseEnum,
  CollectionRequestAddToBaseEnum,
} from "@sparrow/common/types/workspace/collection-base";
import {
  transformRequestData,
  extractAuthData,
} from "../../../../../../../packages/@sparrow-common/src/utils/testFlow.helper";
import { isGuestUserActive } from "@app/store/auth.store";
import { EnvironmentService } from "@app/services/environment.service";
import constants from "@app/constants/constants";
import * as Sentry from "@sentry/svelte";
import { WorkspaceService } from "@app/services/workspace.service";
import { open } from "@tauri-apps/plugin-shell";
import { TeamRepository } from "@app/repositories/team.repository";
import { PlanRepository } from "@app/repositories/plan.repository";
import { TeamService } from "@app/services/team.service";
import { ReduceAuthHeader } from "@sparrow/workspaces/features/rest-explorer/utils";
import { HttpRequestAuthTypeBaseEnum } from "@sparrow/common/types/workspace/http-request-base";
import { getAuthJwt, getSelfhostUrls } from "@app/utils/jwt";
import type {
  ScheduleTestFlowRunDto,
  TestflowDataSetImportDto,
} from "@sparrow/common/types/workspace/testflow-dto";
import {
  startLoading,
  stopLoading,
  testflowDataSets,
  updateTestflowDataSets,
  updateTestflowSchedules,
} from "@sparrow/common/store";
import { captureEvent } from "@app/utils/posthog/posthogConfig";
import { TestflowScheduleNavigatorEnum } from "@sparrow/common/types/workspace/testflow-schedule-tab";
import type { TestflowDataSetItem } from "@sparrow/common/types/workspace/testflow-dateset-tab";
import {
  addTestflowDataSet,
  replaceTestflowDataSet,
} from "@sparrow/common/store";
import { JSONPath } from "jsonpath-plus";
import * as xpath from "xpath";
import { AiAssistantService } from "@app/services/ai-assistant.service";

export class TestflowExplorerPageViewModel {
  private _tab = new BehaviorSubject<Partial<Tab>>({});
  private tabRepository = new TabRepository();
  private collectionRepository = new CollectionRepository();
  private workspaceService = new WorkspaceService();
  private environmentRepository = new EnvironmentRepository();
  private workspaceRepository = new WorkspaceRepository();
  private testflowRepository = new TestflowRepository();

  private guestUserRepository = new GuestUserRepository();
  private testflowService = new TestflowService();
  private compareArray = new CompareArray();
  private environmentService = new EnvironmentService();
  private teamRepository = new TeamRepository();
  private planRepository = new PlanRepository();
  private teamService = new TeamService();
  private aiAssistentService = new AiAssistantService();

  /**
   * Utils
   */
  private _decodeRequest = new DecodeTestflow();

  /**
   * Constructor to initialize the TestflowExplorerPageViewModel class
   * @param doc - TabDocument that contains information about the active tab
   */
  public constructor(doc: TabDocument) {
    if (doc?.isActive) {
      setTimeout(() => {
        const t = createDeepCopy(doc.toMutableJSON());
        delete t.isActive;
        delete t.index;
        this.tab = t;
        this.fetchTestflow();
        this.fetchTestflowDataSets();
      }, 0);
    }
  }

  /**
   * Returns an observable that emits the current state of the tab
   */
  public get tab(): Observable<Partial<Tab>> {
    return this._tab.asObservable();
  }

  /**
   * Sets the value of the tab and updates the observable
   * @param value - the updated tab value
   */
  private set tab(value: Tab) {
    this._tab.next(value);
  }

  /**
   * Return active workspace of the user
   */
  public get activeWorkspace() {
    return this.workspaceRepository.getActiveWorkspace();
  }

  public get environments() {
    return this.environmentRepository.getEnvironment();
  }

  /**
   * Updates the nodes in the testflow with debounce to avoid frequent calls
   * @param _nodes - nodes of the testflow
   */
  private updateNodesDebounce = async (_nodes: TFNodeType[]) => {
    const progressiveTab = createDeepCopy(this._tab.getValue());
    const nodes = _nodes.map((elem) => {
      return {
        id: elem.id,
        type: elem.type,
        data: {
          blockName: elem.data.blockName,
          requestId: elem.data.requestId,
          folderId: elem.data.folderId,
          collectionId: elem.data.collectionId,
          requestData: elem.data.requestData,
        },
        position: { x: elem.position.x, y: elem.position.y },
      };
    });
    progressiveTab.property.testflow.nodes = nodes;
    this.tab = progressiveTab;
    this.tabRepository.updateTab(progressiveTab.tabId, progressiveTab);
    this.compareTestflowWithServer();
  };

  /**
   * Updates the nodes in the testflow with debounce to avoid frequent calls
   * @param _nodes - nodes of the testflow
   */
  private fetchTestflow = async () => {
    const progressiveTab = createDeepCopy(this._tab.getValue());
    // await this.updateEnvironmentState({ isSaveInProgress: true });
    const guestUser = await this.guestUserRepository.findOne({
      name: "guestUser",
    });
    const isGuestUser = guestUser?.getLatest().toMutableJSON().isGuestUser;
    if (isGuestUser) {
      return;
    }
    const response = await this.testflowService.fetchTestflow(
      progressiveTab.path.workspaceId as string,
      progressiveTab.id as string,
    );
    if (response?.isSuccessful) {
      const schedules = response.data.data.schedules;
      updateTestflowSchedules(progressiveTab.id as string, schedules);
    }
  };

  private fetchTestflowDataSets = async () => {
    const progressiveTab = createDeepCopy(this._tab.getValue());
    const guestUser = await this.guestUserRepository.findOne({
      name: "guestUser",
    });
    const isGuestUser = guestUser?.getLatest().toMutableJSON().isGuestUser;
    if (isGuestUser) return;
    const response = await this.testflowService.fetchTestflowDataSets(
      progressiveTab.path.workspaceId as string,
      progressiveTab.id as string,
    );
    if (response?.isSuccessful) {
      const datasets = response.data?.data.datasets;
      updateTestflowDataSets(progressiveTab.id as string, datasets || []);
    }
  };

  private deleteTestDataSet = async (testflowDataSetId: string) => {
    const progressiveTab = createDeepCopy(this._tab.getValue());
    const response = await this.testflowService.deleteTestDataSet(
      progressiveTab.id as string,
      testflowDataSetId,
      progressiveTab?.path?.workspaceId,
    );
    if (response?.isSuccessful) {
      const tabsIdsToDelete = [];
      const mainTabId = await this.tabRepository.getTabById(testflowDataSetId);
      if (mainTabId) tabsIdsToDelete.push(mainTabId.tabId);
      await this.tabRepository.deleteTabsWithTabIdInAWorkspace(
        progressiveTab.path.workspaceId,
        tabsIdsToDelete,
      );
      const datasets = response.data?.data.result;
      updateTestflowDataSets(progressiveTab.id as string, datasets || []);
    }
    return response;
  };

  public renameTestDataSet = async (
    testflowDataSetId: string,
    updatedDataSetName: string,
  ) => {
    const progressiveTab = createDeepCopy(this._tab.getValue());
    const response = await this.testflowService.renameTestDataSet(
      progressiveTab.id as string,
      testflowDataSetId,
      updatedDataSetName,
      progressiveTab?.path?.workspaceId,
    );
    if (response?.isSuccessful) {
      const datasets = response.data?.data.result;
      updateTestflowDataSets(progressiveTab.id as string, datasets || []);
      const mainTab = await this.tabRepository.getTabById(testflowDataSetId);
      if (mainTab) {
        await this.tabRepository.updateTab(mainTab.tabId, {
          name: updatedDataSetName,
        });
      }
    }
    return response;
  };

  /**
   * Debounced method for updating testflow nodes
   */
  public updateNodes = new Debounce().debounce(
    this.updateNodesDebounce as any,
    150,
  );

  /**
   * Updates the edges in the testflow with debounce to avoid frequent calls
   * @param _edges - edges of the testflow
   */
  private updateEdgesDebounce = async (_edges: TFEdgeType[]) => {
    const progressiveTab = createDeepCopy(this._tab.getValue());
    const edges = _edges.map((elem) => {
      return {
        id: elem.id,
        source: elem.source,
        target: elem.target,
      };
    });
    progressiveTab.property.testflow.edges = edges;
    this.tab = progressiveTab;
    this.tabRepository.updateTab(progressiveTab.tabId, progressiveTab);
    this.compareTestflowWithServer();
  };

  /**
   * Debounced method for updating testflow edges
   */
  public updateEdges = new Debounce().debounce(
    this.updateEdgesDebounce as any,
    300,
  );

  /**
   * Placeholder for method to update selected API
   */
  public updateSelectedAPI = async () => {};

  /**
   * Retrieves the list of collections from the current active workspace
   * @returns - the list of collections
   */
  public getCollectionList = () => {
    return this.collectionRepository.getCollection();
  };

  /**
   * Compares the current testflow tab with the server version and updates the saved status accordingly.
   * This method is debounced to reduce the number of server requests.
   * @return A promise that resolves when the comparison is complete.
   */
  private compareTestflowWithServerDebounced = async () => {
    let result = true;
    const progressiveTab = createDeepCopy(this._tab.getValue());

    let testflowServer = await this.testflowRepository.readTestflow(
      progressiveTab.id,
    );
    testflowServer = testflowServer?.toMutableJSON();
    const tabServer = new TestflowTabAdapter().unadapt(progressiveTab);

    if (!testflowServer) {
      result = false;
    }

    // name
    else if (testflowServer.name !== progressiveTab.name) {
      result = false;
    }

    // nodes
    else if (!this.compareArray.init(testflowServer.nodes, tabServer.nodes)) {
      result = false;
    }

    // edges
    else if (
      !this.compareArray.init(
        testflowServer.edges,
        progressiveTab.property.testflow.edges,
      )
    ) {
      result = false;
    }

    // result
    if (result) {
      this.tabRepository.updateTab(progressiveTab.tabId, {
        isSaved: true,
      });
      progressiveTab.isSaved = true;
      this.tab = progressiveTab;
    } else {
      this.tabRepository.updateTab(progressiveTab.tabId, {
        isSaved: false,
        persistence: TabPersistenceTypeEnum.PERMANENT,
      });
      progressiveTab.isSaved = false;
      progressiveTab.persistence = TabPersistenceTypeEnum.PERMANENT;
      this.tab = progressiveTab;
    }
  };

  /**
   * Debounced method to compare the current request tab with the server version.
   */
  private compareTestflowWithServer = new Debounce().debounce(
    this.compareTestflowWithServerDebounced,
    0,
  );

  /**
   * Fetches the active environments for the current workspace
   * @param currentWorkspaceId - the current workspace ID
   * @returns - environment variables filtered for the workspace
   */
  private getActiveEnvironments = async (currentWorkspaceId: string) => {
    let environmentId: string;
    const activeWorkspace =
      await this.workspaceRepository.getActiveWorkspaceDoc();

    if (activeWorkspace) {
      currentWorkspaceId = activeWorkspace.get("_id");
      environmentId = activeWorkspace.get("environmentId");
    }

    const environments = await this.environmentRepository.getEnvironment();
    let environmentDocuments: EnvironmentDocument[] = [];

    environments.subscribe((value) => {
      if (value) {
        environmentDocuments = value;
      }
    });

    let environmentVariables: {
      filtered: ENVExtractVariableType[];
    } = {
      filtered: [],
    };

    if (environmentDocuments && currentWorkspaceId) {
      if (environmentDocuments?.length > 0) {
        const filteredEnv = environmentDocuments
          .filter((elem) => {
            return elem.workspaceId === currentWorkspaceId;
          })
          .filter((elem) => {
            if (
              elem.type === environmentType.GLOBAL ||
              elem.id === environmentId
            ) {
              return true;
            }
          });
        if (filteredEnv?.length > 0) {
          const envs: ENVExtractVariableType[] = [];
          filteredEnv.forEach((elem) => {
            environmentVariables = {
              filtered: [],
            };

            const temp = elem.toMutableJSON() as ENVDocumentType;
            temp.variable.forEach((variable) => {
              if (variable.key && variable.checked) {
                envs.unshift({
                  key: variable.key,
                  value: variable.value,
                  type: temp.type === environmentType.GLOBAL ? "G" : "E",
                  environment: temp.name,
                });
              }
            });
            environmentVariables.filtered = envs;
          });
        }
      }
    }
    return environmentVariables;
  };

  /**
   * Handles running the test flow by processing each node sequentially and recording the results
   */
  public handleSampleTestFlowRun = (): TFNodeStoreType => {
    const progressiveTab = createDeepCopy(this._tab.getValue());
    const adaptedRequest = new InitRequestTab("fewfe", "fewfe");
    adaptedRequest.updateName("Sample API");
    adaptedRequest.updateUrl("https://sample-api.com/");
    const nodes = {
      id: "2",
      response: {
        body: `{"api":"Sample API"}`,
        headers: [],
        status: ResponseStatusCode.OK,
        time: 200,
        size: 400,
        responseContentType: RequestDataTypeEnum.JSON,
      },
      request: adaptedRequest.getValue(),
    };
    return nodes;
  };

  private fetchCollectionAuth = async (_collectionId: string) => {
    const collectionRx =
      await this.collectionRepository.readCollection(_collectionId);
    const collectionDoc = collectionRx?.toMutableJSON();
    let collectionAuth;
    if (collectionDoc?.auth) {
      collectionAuth = {
        auth: collectionDoc?.auth,
        collectionAuthNavigation: collectionDoc?.selectedAuthType,
      } as HttpRequestCollectionLevelAuthTabInterface;
    } else {
      collectionAuth = {
        auth: {
          bearerToken: "",
          basicAuth: {
            username: "",
            password: "",
          },
          apiKey: {
            authKey: "",
            authValue: "",
            addTo: CollectionRequestAddToBaseEnum.HEADER,
          },
        },
        collectionAuthNavigation: CollectionAuthTypeBaseEnum.NO_AUTH,
      };
    }
    return collectionAuth;
  };

  private findConnectedNodes = (
    adj: any[],
    start: number,
    nodes,
    result,
    visited = new Set(),
  ) => {
    if (visited.has(start)) return;

    for (let i = 0; i < nodes.length; i++) {
      if (Number(nodes[i].id) === start) {
        result.push(nodes[i]);
      }
    }

    visited.add(start);

    for (const neighbor of adj[start]) {
      this.findConnectedNodes(adj, neighbor, nodes, result, visited);
    }
  };
  /**
   * Handles running the test flow by processing each node sequentially and recording the results
   */
  public handleTestFlowRun = async (_id: string, _event: string) => {
    const progressiveTab = createDeepCopy(this._tab.getValue());
    const environments = await this.getActiveEnvironments(
      progressiveTab.path.workspaceId,
    );
    const nodes = progressiveTab?.property?.testflow?.nodes;
    const edges = progressiveTab?.property?.testflow?.edges;
    const abortController = new AbortController();
    const { signal } = abortController;

    let runningNodes: any[] = [];

    const workspaceRxDoc = await this.workspaceRepository.readWorkspace(
      progressiveTab.path.workspaceId,
    );
    const workspaceObject = workspaceRxDoc.toMutableJSON();
    const teamRxDoc = await this.teamRepository.getTeamDoc(
      workspaceObject?.team?.teamId as string,
    );
    const teamObject = teamRxDoc?.toMutableJSON();

    if (_event === "run-from-here") {
      const guestUser = await this.guestUserRepository.findOne({
        name: "guestUser",
      });
      if (!guestUser) {
        const planObject = teamObject.plan;
        if (!planObject?.limits?.selectiveTestflowRun?.active) {
          // notifications.error(
          //   "Failed to run from here. please upgrade your plan.",
          // );
          return;
        }
      }
      let maxNodeId = 1;
      for (let i = 0; i < nodes.length; i++) {
        maxNodeId = Math.max(maxNodeId, Number(nodes[i].id));
      }

      // Initialize adjacency list
      const graph = Array.from({ length: maxNodeId + 1 }, () => []);
      // Populate adjacency list

      for (let i = 0; i < edges.length; i++) {
        graph[Number(edges[i].source)].push(Number(edges[i].target));
      }

      let result = [];
      this.findConnectedNodes(graph, Number(_id), nodes, result);
      runningNodes = [...result];
    } else if (_event === "run-till-here") {
      const guestUser = await this.guestUserRepository.findOne({
        name: "guestUser",
      });
      if (!guestUser) {
        const planObject = teamObject.plan;
        if (!planObject?.limits?.selectiveTestflowRun?.active) {
          // notifications.error(
          //   "Failed to run till here. please upgrade your plan.",
          // );
          return;
        }
      }
      let maxNodeId = 1;
      for (let i = 0; i < nodes.length; i++) {
        maxNodeId = Math.max(maxNodeId, Number(nodes[i].id));
      }

      // Initialize adjacency list
      const graph = Array.from({ length: maxNodeId + 1 }, () => []);
      // Populate adjacency list

      for (let i = 0; i < edges.length; i++) {
        graph[Number(edges[i].target)].push(Number(edges[i].source));
      }

      let result = [];
      this.findConnectedNodes(graph, Number(_id), nodes, result);
      runningNodes = [...result.reverse()];
    } else {
      let maxNodeId = 1;
      for (let i = 0; i < nodes.length; i++) {
        maxNodeId = Math.max(maxNodeId, Number(nodes[i].id));
      }

      // Initialize adjacency list
      const graph = Array.from({ length: maxNodeId + 1 }, () => []);
      // Populate adjacency list

      for (let i = 0; i < edges.length; i++) {
        graph[Number(edges[i].source)].push(Number(edges[i].target));
      }

      let result = [];
      this.findConnectedNodes(graph, Number("1"), nodes, result);
      runningNodes = [...result];
    }

    testFlowDataStore.update((testFlowDataMap) => {
      let wsData = testFlowDataMap.get(progressiveTab.tabId);
      if (wsData) {
        wsData.nodes = [];
        wsData.isTestFlowRunning = true;
        wsData.abortController = abortController;
      } else {
        wsData = {
          abortController: abortController,
          nodes: [],
          history: [],
          runner: {},
          isRunHistoryEnable: false,
          isTestFlowRunning: true,
          isTestFlowSaveInProgress: false,
        };
      }
      testFlowDataMap.set(progressiveTab.tabId, wsData);
      return testFlowDataMap;
    });

    let successRequests = 0;
    let failedRequests = 0;
    let totalTime = 0;
    const history: TFHistoryStoreType = {
      status: "fail",
      successRequests: "",
      failedRequests: "",
      totalTime: "",
      timestamp: new Date().toISOString(),
      requests: [],
      expand: false,
    };

    let requestChainResponse = {};

    // Sequential execution
    for (const element of runningNodes) {
      if (element?.type === "requestBlock" && element?.data?.requestId) {
        // Read the API request data from the tab
        const requestData = await this.collectionRepository.readRequestInTab(
          progressiveTab.tabId,
          element.id,
        );

        const request = transformRequestData(requestData);

        const requestTabAdapter = new RequestTabAdapter();

        // --- auth to be used for pre-script + request ---
        const baseAuth =
          request.request.state.requestAuthNavigation ===
          HttpRequestAuthTypeBaseEnum.AUTH_PROFILES
            ? ({
                auth: request.request.auth,
                collectionAuthNavigation: request.request.auth,
              } as HttpRequestCollectionLevelAuthTabInterface)
            : await this.fetchCollectionAuth(element?.data?.collectionId);

        const preRequestResult = await this.executePreScriptTestcases(
          request.request,
          environments.filtered || [],
          baseAuth,
          element.id,
        );

        const {
          success: preScriptSuccess,
          tests: preScriptTests = [],
          error: preScriptError,
          request: preRequest = request.request,
          env: preEnv = environments.filtered || [],
          auth: preAuth = baseAuth,
        } = preRequestResult || {};

        // Build pre-script testResults array
        const preScriptTestResults =
          preScriptSuccess && preScriptTests?.length
            ? preScriptTests.map((t) => ({
                testId: "",
                testName: t.name,
                testStatus: t.passed,
                testMessage: t.error || "",
                initiator: "Pre Script",
              }))
            : preScriptError
            ? [
                {
                  testId: "",
                  testName: "Pre Script Error",
                  testStatus: false,
                  testMessage: preScriptError,
                  initiator: "Pre Script",
                },
              ]
            : [];

        // Use possibly mutated request/env/auth coming from pre-script
        const updatedRequestWrapper = {
          ...request,
          request: preRequest,
        };

        const adaptedRequest: Tab = requestTabAdapter.adapt(
          progressiveTab.path.workspaceId ?? "",
          element?.data?.collectionId ?? "",
          element?.data?.folderId ?? "",
          updatedRequestWrapper,
        );

        const decodeData = this._decodeRequest.init(
          adaptedRequest.property.request,
          preEnv || [],
          requestChainResponse,
        );
        const start = Date.now();

        try {
          const response = await makeHttpRequestV2(
            decodeData[0],
            decodeData[1],
            decodeData[2],
            decodeData[3],
            decodeData[4],
            signal,
          );
          const end = Date.now();
          const duration = end - start;

          let resData: TFHistoryAPIResponseStoreType;
          if (response.isSuccessful) {
            const byteLength = new TextEncoder().encode(
              JSON.stringify(response),
            ).length;
            const responseSizeKB = byteLength / 1024;
            const responseData: TFAPIResponseType = response.data;
            const responseBody = responseData.body;
            const formattedHeaders = Object.entries(
              response?.data?.headers || {},
            ).map(([key, value]) => ({
              key,
              value,
            })) as TFKeyValueStoreType[];
            const responseStatus = response?.data?.status;
            resData = {
              body: responseBody,
              headers: formattedHeaders,
              status: responseStatus,
              time: duration,
              size: responseSizeKB,
              responseContentType:
                this._decodeRequest.setResponseContentType(formattedHeaders),
              testResults: preScriptTestResults,
            };

            if (
              Number(resData.status.split(" ")[0]) >= 200 &&
              Number(resData.status.split(" ")[0]) < 300
            ) {
              successRequests++;
            } else {
              failedRequests++;
            }
            totalTime += duration;
            const req = {
              method: request?.request?.method as string,
              name: request?.name as string,
              status: resData.status,
              time: new ParseTime().convertMilliseconds(duration),
            };
            history.requests.push(req);

            const responseHeader =
              this._decodeRequest.setResponseContentType(formattedHeaders);

            const reqParam = {};
            const params = new URL(decodeData[0]).searchParams;

            for (const [key, value] of params.entries()) {
              reqParam[key] = value;
            }

            const headersObject = Object.fromEntries(
              JSON.parse(decodeData[2]).map(({ key, value }) => [key, value]),
            );

            let reqBody;
            if (decodeData[4] === "application/json") {
              try {
                reqBody = JSON.parse(decodeData[3]);
              } catch (e) {
                reqBody = {};
              }
            } else if (
              decodeData[4] === "multipart/form-data" ||
              decodeData[4] === "application/x-www-form-urlencoded"
            ) {
              const formDataObject = Object.fromEntries(
                JSON.parse(decodeData[3]).map(({ key, value }) => [key, value]),
              );
              reqBody = formDataObject || {};
            } else {
              reqBody = decodeData[3];
            }
            requestChainResponse[
              "$$" +
                element.data.requestData.name.replace(/[^a-zA-Z0-9_]/g, "_")
            ] = {
              response: {
                body:
                  responseHeader === "JSON"
                    ? JSON.parse(resData.body)
                    : resData.body,
                headers: response?.data?.headers,
              },
              request: {
                headers: headersObject || {},
                body: reqBody,
                parameters: reqParam || {},
              },
            };
            requestChainResponse[
              "$$" + element.data.blockName.replace(/[^a-zA-Z0-9_]/g, "_")
            ] = {
              response: {
                body:
                  responseHeader === "JSON"
                    ? JSON.parse(resData.body)
                    : resData.body,
                headers: response?.data?.headers,
              },
              request: {
                headers: headersObject || {},
                body: reqBody,
                parameters: reqParam || {},
              },
            };
          } else {
            resData = {
              body: response.message,
              headers: [],
              status: ResponseStatusCode.ERROR,
              time: duration,
              size: 0,
              testResults: preScriptTestResults,
            };
            failedRequests++;
            totalTime += duration;
            const req = {
              method: request?.request?.method as string,
              name: request?.name as string,
              status: ResponseStatusCode.ERROR,
              time: new ParseTime().convertMilliseconds(duration),
            };
            history.requests.push(req);
          }
          const nodeWithResponse = {
            id: element.id,
            response: resData,
            request: adaptedRequest,
          };

          this.executeNoCodeTestcases(nodeWithResponse);
          await this.executeScriptTestcases(nodeWithResponse);

          // Update store with all test results
          testFlowDataStore.update((testFlowDataMap) => {
            const existingTestFlowData = testFlowDataMap.get(
              progressiveTab.tabId,
            );
            if (existingTestFlowData) {
              existingTestFlowData.nodes.push(nodeWithResponse);
              testFlowDataMap.set(progressiveTab.tabId, existingTestFlowData);
            }
            return testFlowDataMap;
          });
        } catch (error) {
          console.error(error);
          Sentry.captureException(error);
          if (error?.name === "AbortError") {
            break;
          }

          const resData = {
            body: "",
            headers: [],
            status: ResponseStatusCode.ERROR,
            time: 0,
            size: 0,
            testResults: preScriptTestResults,
          };

          const nodeWithResponse = {
            id: element.id,
            response: resData,
            request: adaptedRequest,
          };

          this.executeNoCodeTestcases(nodeWithResponse);
          await this.executeScriptTestcases(nodeWithResponse);

          testFlowDataStore.update((testFlowDataMap) => {
            const existingTestFlowData = testFlowDataMap.get(
              progressiveTab.tabId,
            );
            if (existingTestFlowData) {
              existingTestFlowData.nodes.push(nodeWithResponse);

              requestChainResponse[
                "$$" +
                  element.data.requestData.name.replace(/[^a-zA-Z0-9_]/g, "_")
              ] = {
                response: {
                  body: {},
                  headers: {},
                },
                request: {
                  headers: {},
                  body: {},
                  parameters: {},
                },
              };

              requestChainResponse[
                "$$" + element.data.blockName.replace(/[^a-zA-Z0-9_]/g, "_")
              ] = {
                response: {
                  body: {},
                  headers: {},
                },
                request: {
                  headers: {},
                  body: {},
                  parameters: {},
                },
              };

              testFlowDataMap.set(progressiveTab.tabId, existingTestFlowData);
            }
            return testFlowDataMap;
          });
          failedRequests++;
          totalTime += 0;
          const req = {
            method: request?.request?.method as string,
            name: request?.name as string,
            status: ResponseStatusCode.ERROR,
            time: 0 + " ms",
          };
          history.requests.push(req);
        }
      }
    }

    // Now update the history and log it after all requests are done
    history.totalTime = new ParseTime().convertMilliseconds(totalTime);
    history.successRequests = successRequests.toString();
    history.failedRequests = failedRequests.toString();
    if (failedRequests === 0) {
      history.status = "pass";
    }
    testFlowDataStore.update((testFlowDataMap) => {
      const wsData = testFlowDataMap.get(progressiveTab.tabId);
      if (wsData) {
        if (successRequests + failedRequests >= 1) {
          const hs = wsData.history;
          hs.unshift(history);
          wsData.history = hs;
        }
        wsData.isTestFlowRunning = false;
        wsData.runner = requestChainResponse;
        testFlowDataMap.set(progressiveTab.tabId, wsData);
      }
      return testFlowDataMap;
    });
    if (nodes.length >= 2) {
      notifications.success(
        `Test Completed: ${successRequests} Passed, ${failedRequests} Failed`,
      );
    }
  };

  private setEnvironmentVariables = (
    text: string,
    environmentVariables,
  ): string => {
    let updatedText = text.replace(
      /\[\*\$\[(.*?)\]\$\*\]/gs,
      (_, squareContent) => {
        const updated = squareContent
          .replace(/\\/g, "")
          .replace(/"/g, `'`)
          .replace(/\{\{(.*?)\}\}/g, (_, inner) => {
            return `'{{${inner.trim()}}}'`;
          });
        return `[*$[${updated}]$*]`;
      },
    );
    environmentVariables.forEach((element) => {
      const regex = new RegExp(`{{(${element.key})}}`, "g");
      updatedText = updatedText.replace(regex, element.value);
    });
    return updatedText;
  };

  private setDynamicExpression2 = (text: string, response): any => {
    let status = "fail";
    let contentType = "Text";
    const result = text.replace(/\[\*\$\[(.*?)\]\$\*\]/gs, (_, expr) => {
      try {
        const de = expr.replace(/'\{\{(.*?)\}\}'/g, "undefined"); // convert missing environments to undefined
        // Use Function constructor to evaluate with access to `response`
        const fn = new Function(
          "response",
          `
          with (response) {
            return (${de});
          }
        `,
        );
        const s = fn(response);
        if (typeof s === "string") {
          status = "pass";
          contentType = "Text";
          return s;
        }
        if (typeof s === "object" && s !== null) {
          // unwraps [object Object] to string
          status = "pass";
          contentType = "JSON";
          return `${JSON.stringify(s)}`; // serialize object
        }
        contentType = "JavaScript";
        status = "pass";
        return s;
      } catch (e) {
        status = "fail";
        console.error("Eval error:", e.message);
        return e.message;
      }
    });
    return { result, status, contentType };
  };

  public handlePreviewExpression = async (expression) => {
    const progressiveTab = createDeepCopy(this._tab.getValue());
    const environments = await this.getActiveEnvironments(
      progressiveTab.path.workspaceId,
    );

    let runner = {};
    testFlowDataStore.update((testFlowDataMap) => {
      let wsData = testFlowDataMap.get(progressiveTab.tabId);
      if (wsData) {
        runner = wsData.runner;
      }
      return testFlowDataMap;
    });
    return this.setDynamicExpression2(
      this.setEnvironmentVariables(
        "[*$[" + expression + "]$*]",
        environments?.filtered || [],
      ),
      runner,
    );
  };

  public generateTestCases = async (prompt = "", node_id) => {
    const isGuestUser = await this.guestUserRepository.findOne({
      name: "guestUser",
    });
    if (isGuestUser) {
      return;
    }
    const componentData = this._tab.getValue();
    const tabId = componentData?.tabId;

    const workspaceId = componentData.path.workspaceId;
    const workspaceVal =
      await this.workspaceRepository.readWorkspace(workspaceId);
    let teamId = workspaceVal.team?.teamId;
    const progressiveTab = createDeepCopy(this._tab.getValue());
    const node = progressiveTab?.property?.testflow?.nodes.find(
      (item) => item.id === node_id,
    );
    const testCases = node.data.requestData.tests;
    const originalScript = testCases.script || "";

    try {
      const response = await this.aiAssistentService.generateTestCases({
        text: prompt,
        teamId: teamId,
      });
      if (response.isSuccessful) {
        const generatedContent = response?.data?.data.result;
        notifications.success("Post-request script is generated successfully.");
        return {
          generatedContent: generatedContent,
          originalContent: originalScript,
        };
      } else {
        return response?.data;
      }
    } catch (error) {
      notifications.error(
        "Failed to generate post-request script. Please try again.",
      );
    }
  };

  public generatePreScript = async (prompt = "", node_id) => {
    const isGuestUser = await this.guestUserRepository.findOne({
      name: "guestUser",
    });
    if (isGuestUser) {
      return;
    }
    const componentData = this._tab.getValue();
    const tabId = componentData?.tabId;

    let workspaceId = componentData.path.workspaceId;
    let workspaceVal =
      await this.workspaceRepository.readWorkspace(workspaceId);
    let teamId = workspaceVal.team?.teamId;
    const progressiveTab = createDeepCopy(this._tab.getValue());
    const node = progressiveTab?.property?.testflow?.nodes.find(
      (item) => item.id === node_id,
    );
    const testCases = node.data.requestData.tests;
    const originalPreScript = testCases.preScript || "";
    try {
      const response = await this.aiAssistentService.generatePreScript({
        text: prompt,
        teamId: teamId,
      });
      if (response.isSuccessful) {
        const generatedContent = response?.data?.data.result;
        notifications.success("Pre-request script is generated successfully.");
        return {
          generatedContent: generatedContent,
          originalContent: originalPreScript,
        };
      } else {
        return response?.data;
      }
    } catch (error) {
      notifications.error(
        "Failed to generate pre-request script. Please try again.",
      );
    }
  };

  private evaluateCondition = (
    actual: any,
    expectedRaw: string,
    condition: TestCaseConditionOperatorEnum,
  ): { passed: boolean; message?: string } => {
    let passed = false;
    let message: string | undefined = "";
    const expected: any = expectedRaw;

    try {
      switch (condition) {
        case TestCaseConditionOperatorEnum.EQUALS:
          passed = actual == expected;
          message = passed
            ? ""
            : `Expected ${actual} to equal  ${expected || "(empty)"}`;
          break;
        case TestCaseConditionOperatorEnum.NOT_EQUAL:
          passed = actual != expected;
          message = passed
            ? ""
            : `Expected ${actual} not to equal ${expected || "(empty)"}`;
          break;
        case TestCaseConditionOperatorEnum.EXISTS:
          passed = actual !== undefined && actual !== null;
          message = passed ? "" : `Expected ${actual} to exist`;
          break;
        case TestCaseConditionOperatorEnum.DOES_NOT_EXIST:
          passed = actual === undefined || actual === null;
          message = passed ? "" : `Expected ${actual} not to exist`;
          break;
        case TestCaseConditionOperatorEnum.LESS_THAN:
          passed =
            typeof actual === "number"
              ? actual < Number(expected)
              : actual.length < Number(expected);
          message = passed
            ? ""
            : `Expected ${actual} to be less than ${expected || "(empty)"}`;
          break;
        case TestCaseConditionOperatorEnum.LESS_THAN_OR_EQUAL:
          passed =
            typeof actual === "number"
              ? actual <= Number(expected)
              : actual.length <= Number(expected);
          message = passed
            ? ""
            : `Expected ${actual} to be less than or equal to ${
                expected || "(empty)"
              }`;
          break;
        case TestCaseConditionOperatorEnum.GREATER_THAN:
          passed =
            typeof actual === "number"
              ? actual > Number(expected)
              : actual.length > Number(expected);
          message = passed
            ? ""
            : `Expected ${actual} to be greater than ${expected || "(empty)"}`;
          break;
        case TestCaseConditionOperatorEnum.GREATER_THAN_OR_EQUAL:
          passed =
            typeof actual === "number"
              ? actual >= Number(expected)
              : actual.length >= Number(expected);
          message = passed
            ? ""
            : `Expected ${actual} to be greater than or equal to ${
                expected || "(empty)"
              }`;
          break;
        case TestCaseConditionOperatorEnum.CONTAINS:
          passed = typeof actual === "string" && actual.includes(expected);
          message = passed
            ? ""
            : `Expected ${actual} to contain ${expected || "(empty)"}`;
          break;
        case TestCaseConditionOperatorEnum.DOES_NOT_CONTAIN:
          passed = typeof actual === "string" && !actual.includes(expected);
          message = passed
            ? ""
            : `Expected ${actual} not to contain ${expected || "(empty)"}`;
          break;
        case TestCaseConditionOperatorEnum.IS_EMPTY:
          passed = actual === "" || actual === 0;
          message = passed ? "" : `Expected ${actual} to be empty`;
          break;
        case TestCaseConditionOperatorEnum.IS_NOT_EMPTY:
          passed = actual !== "" && actual !== 0;
          message = passed ? "" : `Expected ${actual} not to be empty`;
          break;
        case TestCaseConditionOperatorEnum.IN_LIST:
          try {
            passed = Array.isArray(actual) && actual.includes(expected);
            message = passed
              ? ""
              : `Expected ${actual} to be in list ${expected || "(empty)"}`;
          } catch {
            message = "Result for IN LIST must be a JSON array";
          }
          break;
        case TestCaseConditionOperatorEnum.NOT_IN_LIST:
          try {
            passed = Array.isArray(actual) && !actual.includes(expected);
            message = passed
              ? ""
              : `Expected ${actual} not to be in list ${expected || "(empty)"}`;
          } catch {
            message = "Result for NOT IN LIST must be a JSON array";
          }
          break;
        default:
          message = `Condition ${condition} not supported`;
      }
    } catch (e) {
      message = (e as Error).message;
    }

    return { passed, message };
  };

  private async executePreScriptTestcases(request, env, auth, nodeId) {
    return new Promise((resolve) => {
      const worker = new Worker(
        new URL(
          "../../../../workers/test-pre-script-worker.ts",
          import.meta.url,
        ),
        {
          type: "module",
        },
      );
      const progressiveTab = createDeepCopy(this._tab.getValue());
      const node = progressiveTab?.property?.testflow?.nodes.find(
        (item) => item.id === nodeId,
      );

      const javaScriptTestCases =
        node?.data?.requestData?.tests?.preScript || "";

      // Just send data to worker – no store updates here
      worker.postMessage({
        javaScriptTestCases,
        request,
        env,
        auth,
      });

      worker.onmessage = (e) => {
        const { success, tests, error, request, env, auth } = e.data;

        worker.terminate(); // cleanup

        // Return everything to the caller
        resolve({
          success,
          tests,
          error,
          request,
          env,
          auth,
        });
      };
    });
  }

  private executeNoCodeTestcases = (node) => {
    // const progressiveTab = createDeepCopy(this._tab.getValue());
    // testFlowDataStore.update((testFlowDataMap) => {
    //   const response = testFlowDataMap.get(progressiveTab?.tabId);

    // response.response.testResults = [];
    const testCases = node?.request?.property?.request?.tests?.noCode || [];
    testCases.map((test) => {
      let actual: any;
      let error: string | undefined;
      let testCasePassed = false;
      let testCaseStatusMessage = "";

      if (test.testTarget === TestCaseSelectionTypeEnum.RESPONSE_TEXT) {
        actual = node.response.body;
      } else if (test.testTarget === TestCaseSelectionTypeEnum.TIME_CONSUMING) {
        actual = node.response.time;
      } else if (
        test.testTarget === TestCaseSelectionTypeEnum.RESPONSE_HEADER
      ) {
        if (!test.condition) {
          error = `Condition not found`;
          actual = undefined;
        } else if (!test.testPath) {
          error = `Test path not found`;
          actual = undefined;
        } else {
          actual = node.response.headers.find(
            (h) => h.key.toLowerCase() === test.testPath?.toLowerCase(),
          )?.value;
        }
      } else if (test.testTarget === TestCaseSelectionTypeEnum.RESPONSE_JSON) {
        try {
          if (!test.condition) {
            error = `Condition not found`;
            actual = undefined;
          } else if (!test.testPath) {
            error = `Test path not found`;
            actual = undefined;
          } else {
            const json = JSON.parse(node.response.body);
            // Use JSONPath to extract value, supports $[3].userId, $[0].address.city, etc.
            const result = JSONPath({ path: test.testPath, json });
            // If result is an array, take the first value
            actual = Array.isArray(result) ? result[0] : result;
          }
        } catch (e) {
          error = "Invalid JSON or path";
          actual = undefined;
        }
      } else if (test.testTarget === TestCaseSelectionTypeEnum.RESPONSE_XML) {
        try {
          if (!test.condition) {
            error = `Condition not found`;
            actual = undefined;
          } else if (!test.testPath) {
            error = `Test path not found`;
            actual = undefined;
          } else {
            const xml = node.response.body;
            const doc = new DOMParser().parseFromString(xml, "text/xml");
            // test.testPath should be a valid XPath, e.g. "/root/country/city"
            const nodes = xpath.select(test.testPath, doc);

            if (Array.isArray(nodes) && nodes.length > 0) {
              // If it's an attribute node
              if (nodes[0].nodeType === 2) {
                actual = nodes[0].nodeValue;
              } else if (nodes[0].firstChild) {
                actual = nodes[0].firstChild.nodeValue;
              } else if ((nodes[0] as any).data) {
                actual = (nodes[0] as any).data;
              } else {
                actual = nodes[0].toString();
              }
            } else {
              actual = undefined;
            }
          }
        } catch (e) {
          error = "Invalid XML or XPath";
          actual = undefined;
        }
      } else {
        error = `Test target not found`;
      }

      if (actual !== undefined) {
        const { passed, message: testMessage } = this.evaluateCondition(
          actual,
          test.expectedResult,
          test.condition,
        );
        testCasePassed = passed;
        testCaseStatusMessage = testMessage;
      }
      const testResponse = {
        testId: test.id,
        testName: test.name,
        testStatus: testCasePassed,
        initiator: "Assertions",
        testMessage: error || testCaseStatusMessage,
      };

      node.response.testResults.push(testResponse);

      // testFlowDataMap.set(progressiveTab.tabId, resData);
    });
  };

  private executeScriptTestcases = async (node) => {
    return new Promise((resolve) => {
      const worker = new Worker(
        new URL("../../../../workers/test-script-worker.ts", import.meta.url),
        {
          type: "module",
        },
      );

      const progressiveTab = createDeepCopy(this._tab.getValue());
      const currentNode = progressiveTab?.property?.testflow?.nodes.find(
        (item) => item.id === node.id,
      );

      const javaScriptTestCases =
        currentNode?.data?.requestData?.tests?.script || "";

      // Send data to worker
      worker.postMessage({
        javaScriptTestCases,
        response: node.response,
      });

      worker.onmessage = (e) => {
        const { success, tests, error } = e.data;

        // Add script test results directly to node.response.testResults (same as no-code)
        if (success && tests?.length) {
          tests.forEach((t) => {
            const testResponse = {
              testId: "",
              testName: t.name,
              testStatus: t.passed,
              testMessage: t.error || "",
              initiator: "Post Script",
            };
            node.response.testResults.push(testResponse);
          });
        } else if (error) {
          const testResponse = {
            testId: "",
            testName: "Post Script Error",
            testStatus: false,
            testMessage: error,
            initiator: "Post Script",
          };
          node.response.testResults.push(testResponse);
        }

        worker.terminate(); // cleanup
        resolve("resolved");
      };
    });
  };

  /**
   * Runs a single test flow node and updates the testFlowDataStore
   * @param nodeId - The id of the node to be executed
   */
  public handleSingleTestFlowNodeRun = async (nodeId: string) => {
    const tab = createDeepCopy(this._tab.getValue());
    const environments = await this.getActiveEnvironments(tab.path.workspaceId);

    const node = tab?.property?.testflow?.nodes.find(
      (item) => item.id === nodeId,
    );

    if (node && node?.type !== "requestBlock") return;

    testFlowDataStore.update((testFlowDataMap) => {
      let wsData = testFlowDataMap.get(tab.tabId);
      if (wsData) {
        wsData.isTestFlowRunning = false;
      } else {
        wsData = {
          nodes: [],
          history: [],
          isRunHistoryEnable: false,
          isTestFlowRunning: false,
          isTestFlowSaveInProgress: false,
        };
      }
      testFlowDataMap.set(tab.tabId, wsData);
      return testFlowDataMap;
    });

    // Read the API request data
    const requestData = await this.collectionRepository.readRequestInTab(
      tab.tabId,
      node.id,
    );

    const request = transformRequestData(requestData);
    const requestTabAdapter = new RequestTabAdapter();

    // --- auth to be used for pre-script + request ---
    const baseAuth =
      request.request.state.requestAuthNavigation ===
      HttpRequestAuthTypeBaseEnum.AUTH_PROFILES
        ? ({
            auth: request.request.auth,
            collectionAuthNavigation: request.request.auth,
          } as HttpRequestCollectionLevelAuthTabInterface)
        : await this.fetchCollectionAuth(node?.data?.collectionId);

    // ---------- PRE SCRIPT EXECUTION (before API call) ----------
    const preRequestResult = await this.executePreScriptTestcases(
      request.request,
      environments.filtered || [],
      baseAuth,
      nodeId,
    );

    const {
      success: preScriptSuccess,
      tests: preScriptTests = [],
      error: preScriptError,
      request: preRequest = request.request,
      env: preEnv = environments.filtered || [],
      auth: preAuth = baseAuth,
    } = preRequestResult || {};

    // Build pre-script testResults array (same structure as no-code)
    const preScriptTestResults =
      preScriptSuccess && preScriptTests?.length
        ? preScriptTests.map((t) => ({
            testId: "",
            testName: t.name,
            testStatus: t.passed,
            testMessage: t.error || "",
            initiator: "Pre Script",
          }))
        : preScriptError
        ? [
            {
              testId: "",
              testName: "Pre Script Error",
              testStatus: false,
              testMessage: preScriptError,
              initiator: "Pre Script",
            },
          ]
        : [];

    // Use possibly mutated request/env/auth coming from pre-script
    const updatedRequestWrapper = {
      ...request,
      request: preRequest,
    };

    const adaptedRequest: Tab = requestTabAdapter.adapt(
      tab.path.workspaceId ?? "",
      node?.data?.collectionId ?? "",
      node?.data?.folderId ?? "",
      updatedRequestWrapper,
    );

    const decodeData = this._decodeRequest.init(
      adaptedRequest.property.request,
      preEnv || [],
    );

    const start = Date.now();
    let resData: TFHistoryAPIResponseStoreType;
    let duration = 0;

    try {
      const response = await makeHttpRequestV2(
        decodeData[0],
        decodeData[1],
        decodeData[2],
        decodeData[3],
        decodeData[4],
      );
      const end = Date.now();
      duration = end - start;

      if (response.isSuccessful) {
        const byteLength = new TextEncoder().encode(
          JSON.stringify(response),
        ).length;
        const responseSizeKB = byteLength / 1024;
        const responseData: TFAPIResponseType = response.data;
        const formattedHeaders = Object.entries(
          responseData?.headers || {},
        ).map(([key, value]) => ({ key, value })) as TFKeyValueStoreType[];

        resData = {
          body: responseData.body,
          headers: formattedHeaders,
          status: responseData.status,
          time: duration,
          size: responseSizeKB,
          responseContentType:
            this._decodeRequest.setResponseContentType(formattedHeaders),
          // IMPORTANT: seed with pre-script results
          testResults: preScriptTestResults,
        };
      } else {
        resData = {
          body: response.message,
          headers: [],
          status: ResponseStatusCode.ERROR,
          time: duration,
          size: 0,
          // even on error, keep pre-script results
          testResults: preScriptTestResults,
        };
      }
    } catch (error) {
      Sentry.captureException(error);
      resData = {
        body: "",
        headers: [],
        status: ResponseStatusCode.ERROR,
        time: 0,
        size: 0,
        testResults: preScriptTestResults,
      };
    }

    const newNode = {
      id: node.id,
      response: resData,
      request: adaptedRequest,
    };
    this.executeNoCodeTestcases(newNode);

    await this.executeScriptTestcases(newNode);
    testFlowDataStore.update((testFlowDataMap) => {
      const wsData = testFlowDataMap.get(tab.tabId);
      if (wsData) {
        const nodeIndex = wsData.nodes.findIndex((n) => n.id === node.id);

        if (nodeIndex !== -1) {
          wsData.nodes[nodeIndex] = newNode;
        } else {
          wsData.nodes.push(newNode);
        }

        testFlowDataMap.set(tab.tabId, wsData);
      }
      return testFlowDataMap;
    });
  };

  public fixTestScript = async (type: string, nodeId): Promise<void> => {
    const componentData = this._tab.getValue();
    const tabId = componentData?.tabId;
    let workspaceId = componentData.path.workspaceId;
    let workspaceVal =
      await this.workspaceRepository.readWorkspace(workspaceId);
    let teamId = workspaceVal.team?.teamId;

    const progressiveTab = createDeepCopy(this._tab.getValue());
    const requestNode = progressiveTab?.property?.testflow?.nodes.find(
      (item) => item.id === nodeId,
    );

    if (!requestNode) {
      notifications.error("No test request node found.");
      return;
    }

    // pick correct script field
    const testCases = requestNode.data.requestData.tests;

    if (type === "Post Script") {
      const response = await this.aiAssistentService.fixTestScript({
        teamId: teamId,
        testScript: testCases.script,
        type: "post-script",
      });

      if (response.isSuccessful) {
        await this.updateRequestTests(
          {
            ...testCases,
            script: response?.data?.data.result,
          },
          nodeId,
        );

        // Then update the store to remove error messages
        testFlowDataStore.update((testFlowDataMap) => {
          const testFlowData = testFlowDataMap.get(tabId);
          if (testFlowData?.nodes) {
            const nodeData = testFlowData.nodes.find(
              (node) => node.id === nodeId,
            );

            if (nodeData?.response?.testResults) {
              nodeData.response.testResults =
                nodeData.response.testResults.filter((testResult) => {
                  return !(
                    testResult.initiator === "Post Script" &&
                    testResult.testName.includes("Script Error")
                  );
                });
            }
          }

          return testFlowDataMap;
        });
        notifications.success("Test script fixed successfully.");
        return response;
      } else if (
        response?.data?.message === "Limit reached. Please try again later."
      ) {
        notifications.error("AI Limit has Reached.please upgrade plan.");
      } else {
        notifications.error("Failed to fix test script.");
      }
    } else if (type === "Pre Script") {
      const response = await this.aiAssistentService.fixTestScript({
        teamId: teamId,
        testScript: testCases.preScript,
        type: "pre-script",
      });

      if (response.isSuccessful) {
        await this.updateRequestTests(
          {
            ...testCases,
            preScript: response?.data?.data.result,
          },
          nodeId,
        );
        testFlowDataStore.update((testFlowDataMap) => {
          const testFlowData = testFlowDataMap.get(tabId);

          if (testFlowData?.nodes) {
            const nodeData = testFlowData.nodes.find(
              (node) => node.id === nodeId,
            );

            if (nodeData?.response?.testResults) {
              nodeData.response.testResults =
                nodeData.response.testResults.filter((testResult) => {
                  return !(
                    testResult.initiator === "Pre Script" &&
                    testResult.testName.includes("Script Error")
                  );
                });
            }
          }

          return testFlowDataMap;
        });
        notifications.success("Test script fixed successfully.");

        return response;
      } else if (
        response?.data?.message === "Limit reached. Please try again later."
      ) {
        notifications.error("AI Limit has Reached.please upgrade plan.");
      } else {
        notifications.error("Failed to fix test script.");
      }
    }
  };

  public updateRequestTests = async (_tests: Tests, nodeId) => {
    const progressiveTab = createDeepCopy(this._tab.getValue());
    const node = progressiveTab?.property?.testflow?.nodes.find(
      (item) => item.id === nodeId,
    );
    node.data.requestData.tests = _tests;
    this.tab = progressiveTab;
    await this.tabRepository.updateTab(progressiveTab.tabId, progressiveTab);
    this.compareTestflowWithServer();
  };

  /**
   * Toggles the visibility of the history container for the test flow.
   * If the tab data is already available, it updates the `isRunHistoryEnable` property.
   * If the tab data is not found, it initializes it with the provided toggle state.
   * @param _toggleState - boolean flag to enable or disable the history container.
   */
  public toggleHistoryContainer = (_toggleState: boolean) => {
    const progressiveTab = createDeepCopy(this._tab.getValue()); // Create a deep copy of the current tab
    testFlowDataStore.update((testFlowDataMap) => {
      let wsData = testFlowDataMap.get(progressiveTab.tabId); // Retrieve data for the current tab

      // Update existing data or initialize if not found
      if (wsData) {
        wsData.isRunHistoryEnable = _toggleState;
      } else {
        wsData = {
          isRunHistoryEnable: _toggleState,
          history: [],
          nodes: [],
          isTestFlowRunning: false,
          isTestFlowSaveInProgress: false,
        };
      }
      testFlowDataMap.set(progressiveTab.tabId, wsData); // Save the updated tab data
      return testFlowDataMap;
    });
  };

  /**
   * Removes nodes with an ID less than the specified ID and updates the testFlowDataStore.
   * @param tabId - The ID of the tab to update.
   * @param id - The ID threshold; nodes with an ID less than this will be removed.
   */
  public deleteNodeResponse = (tabId: string, id: string) => {
    // Create a deep copy of the current tab data
    const progressiveTab = createDeepCopy(this._tab.getValue());

    testFlowDataStore.update((testFlowDataMap) => {
      // Retrieve the data for the specific tab using tabId
      const wsData = testFlowDataMap.get(tabId);

      // If there is data for this tab, proceed to update the nodes
      if (wsData) {
        // Filter out nodes with ID less than the specified ID
        wsData.nodes = wsData.nodes.filter((node) => node.id < id);

        // Update the testFlowDataStore with the modified data
        testFlowDataMap.set(tabId, wsData);
      }

      return testFlowDataMap; // Return the updated data map
    });
  };

  /**
   * Toggles the expansion of a specific history entry in the test flow.
   * @param _toggleState - boolean flag to expand or collapse the history details.
   * @param _index - the index of the history entry to be toggled.
   */
  public toggleHistoryDetails = (_toggleState: boolean, _index: number) => {
    const progressiveTab = createDeepCopy(this._tab.getValue()); // Create a deep copy of the current tab
    testFlowDataStore.update((testFlowDataMap) => {
      const wsData = testFlowDataMap.get(progressiveTab.tabId); // Retrieve data for the current tab
      // If the history data exists, update the 'expand' state for the specific entry
      if (wsData && wsData.history[_index]) {
        wsData.history[_index].expand = _toggleState;
        testFlowDataMap.set(progressiveTab.tabId, wsData); // Save the updated history data
      }
      return testFlowDataMap;
    });
  };

  /**
   * Redirects to a specific request based on the provided workspace, collection, folder, and request IDs.
   *
   * @param _workspaceId - The ID of the workspace where the request is located.
   * @param _collectionId - The ID of the collection containing the request.
   * @param _folderId - The ID of the folder containing the request (if applicable).
   * @param _requestId - The ID of the request to be redirected to.
   *
   * @returns Resolves when the redirection process is completed.
   */
  public redirectRequest = async (
    _collectionId: string,
    _folderId: string,
    _requestId: string,
  ) => {
    const progressiveTab = createDeepCopy(this._tab.getValue());
    const workspaceId = progressiveTab.path.workspaceId;
    const errorMessage = "id can't be empty while redirecting request!";
    // base conditions
    if (!workspaceId) {
      console.error("Workspace " + errorMessage);
      return;
    }
    if (!_collectionId) {
      console.error("Collection " + errorMessage);
      return;
    }
    if (!_requestId) {
      console.error("Request " + errorMessage);
      return;
    }

    // fetching request from the db
    let request;
    if (_folderId?.length > 0) {
      request = await this.collectionRepository.readRequestInFolder(
        _collectionId,
        _folderId,
        _requestId,
      );
    } else {
      request = await this.collectionRepository.readRequestOrFolderInCollection(
        _collectionId,
        _requestId,
      );
    }
    if (!request) {
      console.error("Request not found for redirecting!");
      return;
    }

    // creating a tab for the request
    const requestTabAdapter = new RequestTabAdapter();
    const adaptedRequest = requestTabAdapter.adapt(
      workspaceId || "",
      _collectionId || "",
      _folderId || "",
      request,
    );
    this.tabRepository.createTab(adaptedRequest);
    scrollToTab("");
  };

  /**
   * @description - updates testflow tab name
   * @param _name - new test flow name
   */
  public updateName = async (_name: string, event = "") => {
    const progressiveTab = createDeepCopy(this._tab.getValue());
    const trimmedName = _name.trim();

    if (event === "blur" && trimmedName === "") {
      const data = await this.testflowRepository.readTestflow(
        progressiveTab.id,
      );
      progressiveTab.name = data.name;
    } else if (event === "") {
      progressiveTab.name = _name;
    }
    this.tab = progressiveTab;
    await this.tabRepository.updateTab(progressiveTab.tabId, progressiveTab);
    this.compareTestflowWithServer();
  };

  /**
   * @description - updates testflow tab state
   * @param _state - new test flow state
   */
  public updateTestflowState = async (_state: any) => {
    const progressiveTab = createDeepCopy(this._tab.getValue());
    progressiveTab.property.testflow.state = {
      ...progressiveTab.property.testflow.state,
      ..._state,
    };
    this.tab = progressiveTab;
    await this.tabRepository.updateTab(progressiveTab.tabId, progressiveTab);
  };

  private constructBaseUrl = async (_id: string) => {
    const workspaceData = await this.workspaceRepository.readWorkspace(_id);
    const hubUrl = workspaceData?.team?.hubUrl;

    const [selfhostBackendUrl] = getSelfhostUrls();
    if (selfhostBackendUrl) {
      return selfhostBackendUrl;
    }

    if (hubUrl && constants.APP_ENVIRONMENT_PATH !== "local") {
      const envSuffix = constants.APP_ENVIRONMENT_PATH;
      return `${hubUrl}/${envSuffix}`;
    }
    return constants.API_URL;
  };

  /**
   * @description - saves testflow to the mongo server
   */
  public saveTestflow = async () => {
    const progressiveTab = this._tab.getValue() as Tab;
    testFlowDataStore.update((testFlowDataMap) => {
      let wsData = testFlowDataMap.get(progressiveTab.tabId); // Retrieve data for the current tab

      // Update existing data or initialize if not found
      if (wsData) {
        wsData.isTestFlowSaveInProgress = true;
      } else {
        wsData = {
          isRunHistoryEnable: false,
          history: [],
          nodes: [],
          isTestFlowRunning: false,
          isTestFlowSaveInProgress: true,
        };
      }
      testFlowDataMap.set(progressiveTab.tabId, wsData); // Save the updated tab data
      return testFlowDataMap;
    });
    const currentTestflow = this._tab.getValue();
    const activeWorkspace = await this.workspaceRepository.readWorkspace(
      currentTestflow?.path?.workspaceId as string,
    );
    // await this.updateEnvironmentState({ isSaveInProgress: true });
    const guestUser = await this.guestUserRepository.findOne({
      name: "guestUser",
    });
    const unadaptedTestflow = new TestflowTabAdapter().unadapt(
      currentTestflow as Tab,
    ); // Adapt the testflow tab
    const isGuestUser = guestUser?.getLatest().toMutableJSON().isGuestUser;
    if (isGuestUser) {
      await this.testflowRepository.updateTestflow(
        currentTestflow?.id as string,
        {
          ...unadaptedTestflow,
          updatedAt: new Date().toISOString(),
        },
      );
      const progressiveTab = this._tab.getValue();
      progressiveTab.isSaved = true;
      this.tab = progressiveTab as Tab;
      await this.tabRepository.updateTab(
        progressiveTab?.tabId as string,
        progressiveTab,
      );
      // await this.updateEnvironmentState({
      //   isSaveInProgress: false,
      // });
      notifications.success(
        `Changes saved for ${currentTestflow.name} testflow.`,
      );

      testFlowDataStore.update((testFlowDataMap) => {
        let wsData = testFlowDataMap.get(progressiveTab?.tabId as string); // Retrieve data for the current tab

        // Update existing data or initialize if not found
        if (wsData) {
          wsData.isTestFlowSaveInProgress = false;
        }
        testFlowDataMap.set(
          progressiveTab.tabId as string,
          wsData as TFDataStoreType,
        ); // Save the updated tab data
        return testFlowDataMap;
      });
      return;
    }

    const baseUrl = await this.constructBaseUrl(activeWorkspace._id);
    const response = await this.testflowService.updateTestflow(
      activeWorkspace._id,
      currentTestflow?.id as string,
      unadaptedTestflow,
      baseUrl,
    );
    if (response.isSuccessful) {
      this.testflowRepository.updateTestflow(
        response.data.data._id,
        response.data.data,
      );
      const progressiveTab = this._tab.getValue();
      progressiveTab.isSaved = true;
      this.tab = progressiveTab as Tab;
      await this.tabRepository.updateTab(
        progressiveTab?.tabId as string,
        progressiveTab,
      );
      // await this.updateEnvironmentState({
      //   isSaveInProgress: false,
      // });
      notifications.success(
        `Changes saved for ${currentTestflow.name} testflow.`,
      );
    } else {
      // await this.updateEnvironmentState({ isSaveInProgress: false });
      if (response.message === "Network Error") {
        notifications.error(response.message);
      } else if (response.message === ResponseMessage.PLAN_LIMIT_MESSAGE) {
        return response;
      } else {
        notifications.error(
          `Failed to save changes for ${currentTestflow.name} testflow.`,
        );
      }
    }
    testFlowDataStore.update((testFlowDataMap) => {
      let wsData = testFlowDataMap.get(progressiveTab?.tabId as string); // Retrieve data for the current tab

      // Update existing data or initialize if not found
      if (wsData) {
        wsData.isTestFlowSaveInProgress = false;
      }
      testFlowDataMap.set(
        progressiveTab.tabId as string,
        wsData as TFDataStoreType,
      ); // Save the updated tab data
      return testFlowDataMap;
    });
  };

  /**
   * @description - updates environment tab name
   * @param _name - new environment name
   */
  public updateNameWithTestFlowList = async (_name: string) => {
    const progressiveTab = createDeepCopy(this._tab.getValue());
    if (progressiveTab?.name && _name !== progressiveTab.name) {
      progressiveTab.name = _name;
    }
    this.tab = progressiveTab;
  };

  /**
   * @description - This function will clear the test flow store data.
   */
  public clearTestFlowData = () => {
    const currentTestflow = this._tab.getValue();
    testFlowDataStore.update((testFlowDataMap) => {
      const wsData: TFDataStoreType | undefined = testFlowDataMap.get(
        currentTestflow?.tabId as string,
      );
      if (wsData) {
        const clearedResponse = wsData.nodes.map((node) => ({
          ...node,
          response: { body: "", headers: [], status: "", time: 0, size: 0 },
        }));
        wsData.nodes = clearedResponse;
        testFlowDataMap.set(currentTestflow?.tabId as string, wsData);
      }
      return testFlowDataMap;
    });
    notifications.success(`Cleared all Responses for testflow.`);
  };

  /**
   * @description - Fetches request data based on collection, request, and folder IDs.
   * @param {string} collectionId - The ID of the collection containing the request.
   * @param {string} requestId - The ID of the specific request to retrieve.
   * @param {string} folderId - The ID of the folder containing the request.
   * @returns {Promise<any>} - Returns the request data asynchronously.
   */
  public getRequestdata = async (
    collectionId: string,
    requestId: string,
    folderId: string,
  ) => {
    let request;
    if (folderId) {
      request = await this.collectionRepository.readRequestInFolder(
        collectionId,
        folderId,
        requestId,
      );
      return request;
    } else {
      request = await this.collectionRepository.readRequestOrFolderInCollection(
        collectionId,
        requestId,
      );
      return request;
    }
  };

  /**
   * @description
   * Read an API request data within a tab.
   */
  public getRequestDataFromTab = async (tabId: string, requestId: string) => {
    let request = await this.collectionRepository.readRequestInTab(
      tabId,
      requestId,
    );
    return request;
  };

  /**
   * @description
   * Read an API request data within a node.
   */
  public checkRequestExistInNode = async (tabId: string, nodeId: string) => {
    let request = await this.collectionRepository.readRequestExistInNode(
      tabId,
      nodeId,
    );
    return request;
  };

  /**
   * @description
   * Update a block data.
   */
  public updateBlockData = async (
    tabId: string,
    nodeId: string,
    requestData: object,
  ) => {
    await this.collectionRepository.updateBlockData(tabId, nodeId, requestData);
  };

  /**
   *
   * @param  - response state
   */
  public updateResponseState = async (key, val) => {
    const progressiveTab = createDeepCopy(this._tab.getValue());
    if (key === "responseNavigation") {
      testFlowDataStore.update((restApiDataMap) => {
        const data = restApiDataMap.get(progressiveTab?.tabId);
        if (data) {
          data.response.navigation = val;
        }
        restApiDataMap.set(progressiveTab.tabId, data);
        return restApiDataMap;
      });
    } else if (key === "responseBodyLanguage") {
      testFlowDataStore.update((restApiDataMap) => {
        const data = restApiDataMap.get(progressiveTab?.tabId);
        if (data) {
          data.response.bodyLanguage = val;
        }
        restApiDataMap.set(progressiveTab.tabId, data);
        return restApiDataMap;
      });
    } else if (key === "responseBodyFormatter") {
      testFlowDataStore.update((restApiDataMap) => {
        const data = restApiDataMap.get(progressiveTab?.tabId);
        if (data) {
          data.response.bodyFormatter = val;
        }
        restApiDataMap.set(progressiveTab.tabId, data);
        return restApiDataMap;
      });
    }
  };

  /**
   * Get workspace data through workspace id
   * @param workspaceId - id of workspace
   * @returns - workspace document
   */
  public getWorkspaceById = async (workspaceId: string) => {
    return await this.workspaceRepository.readWorkspace(workspaceId);
  };

  /**
   *
   * @param isGlobalVariable - defines to save local or global
   * @param environmentVariables - pre existing environment data
   * @param newVariableObj - new entry to be extended
   * @returns
   */
  public updateEnvironment = async (
    isGlobalVariable: boolean,
    environmentVariables,
    newVariableObj: KeyValue,
  ) => {
    let isGuestUser;
    isGuestUserActive.subscribe((value) => {
      isGuestUser = value;
    });
    if (isGlobalVariable) {
      // api payload
      let payload = {
        name: environmentVariables.global.name,
        variable: [
          ...environmentVariables.global.variable,
          {
            key: newVariableObj.key,
            value: newVariableObj.value,
            checked: true,
          },
        ],
      };
      // removes blank key value pairs
      payload.variable = [
        ...payload.variable.filter((variable) => {
          return variable.key.length > 0;
        }),
        {
          key: "",
          value: "",
          checked: false,
        },
      ];

      if (isGuestUser === true) {
        // updates environment list
        this.environmentRepository.updateEnvironment(
          environmentVariables.global.id,
          payload,
        );

        let currentTab = await this.tabRepository.getTabById(
          environmentVariables.global.id,
        );
        if (currentTab) {
          let currentTabId = currentTab.tabId;
          const envTab = createDeepCopy(currentTab);
          envTab.property.environment.variable = payload.variable;
          envTab.isSaved = true;
          await this.tabRepository.updateTab(currentTabId as string, {
            property: envTab.property,
            isSaved: envTab.isSaved,
          });
        }

        notifications.success("Environment variable added successfully.");
        return {
          isSuccessful: true,
        };
      }
      const baseUrl = await this.constructBaseUrl(
        this._tab.getValue().path.workspaceId,
      );
      const response = await this.environmentService.updateEnvironment(
        this._tab.getValue().path.workspaceId,
        environmentVariables.global.id,
        payload,
        baseUrl,
      );
      if (response.isSuccessful) {
        // updates environment list
        this.environmentRepository.updateEnvironment(
          response.data.data._id,
          response.data.data,
        );

        let currentTab = await this.tabRepository.getTabById(
          response.data.data._id,
        );

        if (currentTab) {
          let currentTabId = currentTab.tabId;
          const envTab = createDeepCopy(currentTab);
          envTab.property.environment.variable = response.data.data.variable;
          envTab.isSaved = true;
          await this.tabRepository.updateTab(currentTabId as string, {
            property: envTab.property,
            isSaved: envTab.isSaved,
          });
        }

        notifications.success("Environment variable added successfully.");
      } else {
        notifications.error(
          "Failed to add environment variable. Please try again.",
        );
      }
      return response;
    } else {
      // api payload
      const payload = {
        name: environmentVariables.local.name,
        variable: [
          ...environmentVariables.local.variable,
          {
            key: newVariableObj.key,
            value: newVariableObj.value,
            checked: true,
          },
        ],
      };
      // removes blank key value pairs
      payload.variable = [
        ...payload.variable.filter((variable) => {
          return variable.key.length > 0;
        }),
        {
          key: "",
          value: "",
          checked: false,
        },
      ];
      if (isGuestUser) {
        // updates environment list
        this.environmentRepository.updateEnvironment(
          environmentVariables.local.id,
          payload,
        );

        let currentTab = await this.tabRepository.getTabById(
          environmentVariables.local.id,
        );

        if (currentTab) {
          let currentTabId = currentTab.tabId;
          const envTab = createDeepCopy(currentTab);
          envTab.property.environment.variable = payload.variable;
          envTab.isSaved = true;
          await this.tabRepository.updateTab(currentTabId as string, {
            property: envTab.property,
            isSaved: envTab.isSaved,
          });
        }

        notifications.success("Environment variable added successfully.");
        return {
          isSuccessful: true,
        };
      }
      const baseUrl = await this.constructBaseUrl(
        this._tab.getValue().path.workspaceId,
      );
      // api response
      const response = await this.environmentService.updateEnvironment(
        this._tab.getValue().path.workspaceId,
        environmentVariables.local.id,
        payload,
        baseUrl,
      );
      if (response.isSuccessful) {
        // updates environment list
        this.environmentRepository.updateEnvironment(
          response.data.data._id,
          response.data.data,
        );

        let currentTab = await this.tabRepository.getTabById(
          response.data.data._id,
        );
        if (currentTab) {
          const currentTabId = currentTab.tabId;
          const envTab = createDeepCopy(currentTab);
          envTab.property.environment.variable = response.data.data.variable;
          envTab.isSaved = true;
          await this.tabRepository.updateTab(currentTabId as string, {
            property: envTab.property,
            isSaved: envTab.isSaved,
          });
        }

        notifications.success("Environment variable added successfully.");
      } else {
        notifications.error(
          "Failed to add environment variable. Please try again.",
        );
      }
      return response;
    }
  };

  /**
   * @description - This function stops the api calls in Testflow.
   */
  public handleStopApis = () => {
    let abortController;
    testFlowDataStore.update((testFlowDataMap) => {
      const currentTestflow = this._tab.getValue();
      const wsData = testFlowDataMap.get(currentTestflow?.tabId as string);
      if (wsData) {
        abortController = wsData.abortController;
      }
      return testFlowDataMap;
    });
    if (abortController) {
      abortController.abort();
      testFlowDataStore.update((testFlowDataMap) => {
        const currentTestflow = this._tab.getValue();
        const wsData = testFlowDataMap.get(currentTestflow?.tabId as string);
        if (wsData) {
          wsData.isTestFlowRunning = false;
          testFlowDataMap.set(currentTestflow?.tabId as string, wsData);
        }
        return testFlowDataMap;
      });
    }
  };

  /**
   * @description - This function will redirect to the sparrow docs of testflow.
   */
  public redirectDocsTestflow = async () => {
    await open(constants.TESTFLOW_DOCS_URL);
    return;
  };

  /**
   * @description - This function will provide the block limit to users according to their plan.
   */
  public userLimitBlockPerTestflow = async () => {
    const response = await this.workspaceRepository.getActiveWorkspaceDoc();
    const teamId = response?._data?.team?.teamId || "";
    const teamData = await this.teamRepository.getTeamDoc(teamId);
    const teamDoc = teamData.toMutableJSON();
    if (teamDoc) {
      return teamDoc?.plan?.limits;
    }
  };

  /**
   * @description - This function will provide the Count of TestFlow are Created.
   */
  public fetchCountofTestFlow = async () => {
    let count = 0;
    const data = await this.testflowRepository.getTestflowDoc();
    count = data?.length;
    return count;
  };

  /**
   * @description - This function will provide user Limits based on teamId.
   */
  public userPlanLimits = async (teamId: string) => {
    const teamDetails = await this.teamRepository.getTeamDoc(teamId);
    const currentPlan = teamDetails?.toMutableJSON().plan;
    if (currentPlan) {
      return currentPlan?.limits;
    }
  };

  /**
   * @description - This function will send Email request to the Owner.
   */
  public requestToUpgradePlan = async (teamId: string) => {
    const baseUrl = await this.constructBaseUrl(teamId);
    const res = await this.teamService.requestOwnerToUpgradePlan(
      teamId,
      baseUrl,
    );
    if (res?.isSuccessful) {
      notifications.success(
        `Request is Sent Successfully to Owner for Upgrade Plan.`,
      );
    } else {
      notifications.error(`Failed to Send Request for Upgrade Plan`);
    }
  };

  /**
   * @description - This function will redirect you to billing section.
   */
  public handleRedirectToAdminPanel = async (teamId: string) => {
    const [authToken] = getAuthJwt();
    const [, , selfhostAdminUrl] = getSelfhostUrls();
    if (selfhostAdminUrl) {
      await open(selfhostAdminUrl);
    } else {
      await open(
        `${constants.ADMIN_URL}/billing/billingOverview/${teamId}?redirectTo=changePlan&xid=${authToken}`,
      );
    }
  };

  public openTestflowScheduleTab = async (_schedule: any) => {
    const progressiveTab = createDeepCopy(this._tab.getValue());
    const initTestflowScheduleTab = new InitTestflowScheduleTab(
      _schedule.id,
      progressiveTab.path.workspaceId,
    )
      .updatePath({ testflowId: progressiveTab.id })
      .updateName(_schedule.name)
      .updateEnvironmentId(_schedule.environmentId)
      .updateRunConfiguration({
        runCycle: _schedule.runConfiguration.runCycle,
        executeAt: _schedule.runConfiguration.executeAt,
        time: _schedule.runConfiguration.time,
        intervalHours: _schedule.runConfiguration.intervalHours,
        days: _schedule.runConfiguration.days,
      })
      .updateNotification({
        emails: _schedule.notification.emails,
        receiveNotifications: _schedule.notification.receiveNotifications,
      })
      .updateTestflowDataSetId(_schedule.testflowDataSetId)
      .getValue();
    await this.tabRepository.createTab(initTestflowScheduleTab);
  };

  public openTestflowScheduleConfigurationsTab = async (_schedule: string) => {
    const progressiveTab = createDeepCopy(this._tab.getValue());
    const initTestflowScheduleTab = new InitTestflowScheduleTab(
      _schedule.id,
      progressiveTab.path.workspaceId,
    )
      .updatePath({ testflowId: progressiveTab.id })
      .updateName(_schedule.name)
      .updateEnvironmentId(_schedule.environmentId)
      .updateRunConfiguration({
        runCycle: _schedule.runConfiguration.runCycle,
        executeAt: _schedule.runConfiguration.executeAt,
        time: _schedule.runConfiguration.time,
        intervalHours: _schedule.runConfiguration.intervalHours,
        days: _schedule.runConfiguration.days,
      })
      .updateNotification({
        emails: _schedule.notification.emails,
        receiveNotifications: _schedule.notification.receiveNotifications,
      })
      .updateState({
        scheduleNavigator: TestflowScheduleNavigatorEnum.CONFIGURATION,
      })
      .updateTestflowDataSetId(_schedule.testflowDataSetId)
      .getValue();
    await this.tabRepository.createTab(initTestflowScheduleTab);
  };

  public handleContactSales = async () => {
    await open(`${constants.MARKETING_URL}/pricing/`);
  };

  /**
   * @description - This function will convert the data of auth into key value Format.
   */
  public parseAuthHeader = (
    selectAuthHeader?: HttpRequestAuthTypeBaseEnum,
    authContent: any,
  ): { key: string; value: string } | undefined => {
    if (selectAuthHeader) {
      const response = new ReduceAuthHeader(selectAuthHeader, authContent);
      return response.getValue();
    }
  };

  /**
   * Schedules a test flow run with the specified configuration
   */
  public scheduleTestFlowRun = async (
    scheduleName: string,
    environmentId: string,
    runConfiguration: ScheduleTestFlowRunDto["runConfiguration"],
    notification: ScheduleTestFlowRunDto["notification"],
    testflowDataSetId?: string,
  ) => {
    captureEvent("set_schedule_run_cta_clicked", {
      event_source: "desktop_app",
    });
    try {
      const baseUrl = await this.constructBaseUrl(
        this._tab.getValue().path.workspaceId,
      );
      const progressiveTab = createDeepCopy(this._tab.getValue());
      const workspaceId = progressiveTab.path.workspaceId;
      const testflowId = progressiveTab.id;

      if (!workspaceId || !testflowId) {
        notifications.error("Missing workspace or testflow information");
        return { isSuccessful: false, message: "Missing required information" };
      }

      const payload: ScheduleTestFlowRunDto = {
        name: scheduleName,
        environmentId: environmentId || "",
        workspaceId,
        testflowId,
        runConfiguration,
        notification,
        testflowDataSetId,
      };

      const response = await this.testflowService.scheduleTestFlowRun(
        payload,
        testflowId,
        workspaceId,
        baseUrl,
      );

      if (response?.isSuccessful) {
        notifications.success(`New schedule created successfully.`);
        const schedules = response.data.data.testflow.schedules;
        const latestSchedule = response.data.data.schedule;
        updateTestflowSchedules(progressiveTab.id as string, schedules);
        captureEvent("schedule_created", {
          event_source: "desktop_app",
          schedule_id: latestSchedule.data.id,
          testflowId: response.data.data.testflow._id,
          schedule_run_frequency: runConfiguration.runCycle,
          status: latestSchedule.data.isActive,
        });
        this.openTestflowScheduleTab(latestSchedule.data);
        return {
          isSuccessful: true,
          data: response.data,
        };
      } else {
        if (response?.message !== "Plan limit reached") {
          notifications.error(
            `${
              response.message || "Failed to create schedule. Please try again."
            }`,
          );
        }
        return {
          isSuccessful: false,
          message:
            response.message || "Failed to create schedule. Please try again.",
        };
      }
    } catch (error) {
      Sentry.captureException(error);
      const isNetworkOrServerError =
        error?.message === "Network Error" ||
        error?.code === "ECONNABORTED" ||
        (error?.response && error.response.status >= 500) ||
        !error?.response;

      const errorMessage = isNetworkOrServerError
        ? "Unable to create schedule due to an internal server error. Please try again."
        : "Failed to create schedule. Please try again.";

      notifications.error(errorMessage);
      return {
        isSuccessful: false,
        message:
          error?.message || "Failed to create schedule. Please try again.",
      };
    }
  };

  private deleteTestflowSchedule = async (
    _scheduleId: string,
    _scheduleName: string,
  ) => {
    const progressiveTab = createDeepCopy(this._tab.getValue());
    const baseUrl = await this.constructBaseUrl(
      progressiveTab.path.workspaceId,
    );
    const testflow = await this.testflowRepository.readTestflow(
      progressiveTab.id,
    );
    const schedule = testflow._data?.schedules?.find(
      (s) => s.id === _scheduleId,
    );
    const response = await this.testflowService.deleteTestflowSchedule(
      progressiveTab.path.workspaceId,
      progressiveTab.id,
      _scheduleId,
      baseUrl,
    );
    if (response?.isSuccessful) {
      captureEvent("schedule_deleted", {
        event_source: "desktop_app",
        schedule_id: _scheduleId,
        testflow_id: progressiveTab.id,
        schedule_run_frequency: schedule?.runConfiguration?.runCycle,
        status: schedule?.isActive,
      });
      const tabsIdsToDelete = [];
      let childTabs = [];
      // Remove the main tab
      const mainTabId = await this.tabRepository.getTabById(_scheduleId);
      if (mainTabId) tabsIdsToDelete.push(mainTabId.tabId);
      childTabs =
        await this.tabRepository.getTabsByTestflowScheduleId(_scheduleId);
      // Delete all child tabs if any exist
      if (childTabs.length > 0) {
        const allChildTabs = childTabs.map((tab) => tab.tabId);
        tabsIdsToDelete.push(...allChildTabs);
      }
      await this.tabRepository.deleteTabsWithTabIdInAWorkspace(
        progressiveTab.path.workspaceId,
        tabsIdsToDelete,
      );
      const schedules = response.data.data.schedules;
      updateTestflowSchedules(progressiveTab?.id as string, schedules);
      notifications.success(
        `'${_scheduleName}' schedule deleted successfully.`,
      );
    } else {
      notifications.error(`Failed to delete schedule. Please try again.`);
    }
  };

  private runTestflowSchedule = async (_scheduleId: string) => {
    const progressiveTab = createDeepCopy(this._tab.getValue());
    const baseUrl = await this.constructBaseUrl(
      progressiveTab.path.workspaceId,
    );
    notifications.success("Run started successfully.");
    for (let i = 1; i < 5; i++) {
      setTimeout(() => {
        this.fetchTestflow();
      }, i * 500);
    }
    const payload = {
      testflowDataSetId:
        progressiveTab?.property?.testflowSchedule?.testflowDataSetId || "",
    };
    const response = await this.testflowService.runTestflowSchedule(
      progressiveTab.path.workspaceId,
      progressiveTab.id,
      _scheduleId,
      baseUrl,
      payload,
    );
    if (response?.isSuccessful) {
      const schedules = response.data.data.schedules;
      const schedule = schedules.find((s: any) => s.id === _scheduleId);
      captureEvent("schedule_run_now_clicked", {
        event_source: "desktop_app",
        schedule_id: _scheduleId,
        testflow_id: progressiveTab.id,
        schedule_run_frequency: schedule.runConfiguration.runCycle,
        status: schedule.isActive,
      });
      updateTestflowSchedules(progressiveTab?.id as string, schedules);
      // notifications.success("Run executed successfully.");
    } else {
      // notifications.error("Run failed. View details in Test Results.");
    }
  };

  private editTestflowSchedule = async (_scheduleId: string) => {
    const progressiveTab = createDeepCopy(this._tab.getValue());
    const initTestflowScheduleTab = new InitTestflowScheduleTab(
      _scheduleId,
      progressiveTab.path.workspaceId,
    )
      .updatePath({ testflowId: progressiveTab.id })
      .getValue();
    await this.tabRepository.createTab(initTestflowScheduleTab);
  };

  public performTestflowScheduleOperations = async (
    _type: "run" | "edit" | "delete" | "open",
    _scheduleId: string,
    _scheduleName: string,
  ) => {
    if (_type === "run") {
      this.runTestflowSchedule(_scheduleId);
    } else if (_type === "edit") {
      this.editTestflowSchedule(_scheduleId);
    } else if (_type === "delete") {
      this.deleteTestflowSchedule(_scheduleId, _scheduleName);
    }
  };

  public performTestDataSetOperations = async (
    _type: "delete" | "export" | "rename",
    testflowDataSetId: string,
    updatedDataSetName?: string,
  ) => {
    if (_type === "delete") {
      return this.deleteTestDataSet(testflowDataSetId);
    } else if (_type === "rename") {
      return this.renameTestDataSet(testflowDataSetId, updatedDataSetName);
    }
  };

  public updateTestflowScheduleStatus = async (
    _scheduleId: string,
    isChecked: any,
  ) => {
    const progressiveTab = createDeepCopy(this._tab.getValue());
    const baseUrl = await this.constructBaseUrl(
      progressiveTab.path.workspaceId,
    );
    const response = await this.testflowService.updateTestflowSchedule(
      progressiveTab.path.workspaceId,
      progressiveTab.id,
      _scheduleId,
      { isActive: isChecked },
      baseUrl,
    );
    if (response?.isSuccessful) {
      const schedules = response.data.data.schedules;
      const matchedSchedule = schedules.find((sch) => sch.id === _scheduleId);
      captureEvent("schedule_status_changed", {
        event_source: "desktop_app",
        cta_location: "scheduled_run_tab",
        schedule_id: _scheduleId,
        testflow_id: progressiveTab.id,
        schedule_run_frequency: matchedSchedule?.runConfiguration?.runCycle,
        previous_status: !isChecked ? "active" : "inactive",
        new_status: isChecked ? "active" : "inactive",
      });
      updateTestflowSchedules(progressiveTab?.id as string, schedules);
    }
  };

  /**
   * @description - creates new local environment tab
   * @param env - environment that needs to be opened
   */
  public handleOpenEnvironment = async (env) => {
    const currentWorkspace = await this.workspaceRepository.readWorkspace(
      env.workspaceId,
    );

    const initEnvironmentTab = new InitEnvironmentTab(
      env.id,
      currentWorkspace._id,
    );
    initEnvironmentTab.setName(env.name).setVariable(env.variable);

    this.tabRepository.createTab(initEnvironmentTab.getValue());
    scrollToTab(initEnvironmentTab.getValue().id);
  };

  /**
   * @description - This function will provide the downgraded status of the team.
   * @returns {Promise<boolean>} - Returns true if the team is downgraded, otherwise false or undefined.
   */
  public getTeamDowngradeStatus = async () => {
    const response = await this.workspaceRepository.getActiveWorkspaceDoc();
    const teamId = response?._data?.team?.teamId || "";
    const teamData = await this.teamRepository.getTeamDoc(teamId);
    const teamDoc = teamData.toMutableJSON();
    if (teamDoc) {
      return teamDoc?.isDowngraded;
    }
  };

  /**
   * @description - This function will provide the full team details for the active workspace.
   */
  public getTeamDetails = async () => {
    const response = await this.workspaceRepository.getActiveWorkspaceDoc();
    const teamId = response?._data?.team?.teamId || "";
    const teamData = await this.teamRepository.getTeamDoc(teamId);
    const teamDoc = teamData.toMutableJSON();
    if (teamDoc) {
      return teamDoc;
    }
    return null;
  };

  public importTestflowDataSet = async (
    dataSet: any,
    dataSetType: string,
    name: string,
  ) => {
    try {
      const progressiveTab = createDeepCopy(this._tab.getValue());
      const payload = {
        item: dataSet,
        formatType: dataSetType,
        name,
      } as TestflowDataSetImportDto;
      const response = await this.testflowService.importTestflowDataSet(
        progressiveTab.id as string,
        payload,
        progressiveTab?.path?.workspaceId,
      );
      if (response?.isSuccessful) {
        const dataset = response.data?.data.data;
        addTestflowDataSet(progressiveTab.id as string, dataset || []);
        notifications.success(`Data set imported successfully.`);
      }
      return response;
    } catch (err) {
      notifications.error("Failed to import data set. Please try again.");
    }
  };

  public importTestflowDataSetFileChange = async (
    dataSet: any,
    dataSetType: string,
    name: string,
  ) => {
    try {
      const progressiveTab = createDeepCopy(this._tab.getValue());
      const payload = {
        item: dataSet,
        formatType: dataSetType,
        name,
      } as TestflowDataSetImportDto;
      const response =
        await this.testflowService.importTestflowDataSetFileChange(
          progressiveTab.id as string,
          payload,
          progressiveTab?.path?.workspaceId,
        );
      if (response?.isSuccessful) {
        const dataset = response?.data?.data?.data;
        addTestflowDataSet(progressiveTab.id as string, dataset || []);
        notifications.success(`Data set imported successfully.`);
      }
      return response;
    } catch (err) {
      notifications.error("Failed to import data set.");
    }
  };

  public updateDatasetByName = async (
    dataSet: any,
    dataSetType: string,
    name: string,
  ) => {
    try {
      const progressiveTab = createDeepCopy(this._tab.getValue());
      const payload = {
        item: dataSet,
        formatType: dataSetType,
        name,
      } as TestflowDataSetImportDto;
      const response = await this.testflowService.updateDatasetByName(
        progressiveTab.id as string,
        payload,
        progressiveTab?.path?.workspaceId,
      );
      if (response?.isSuccessful) {
        const dataset = response?.data?.data;
        replaceTestflowDataSet(progressiveTab.id as string, dataset || []);
        notifications.success(`Data set imported successfully.`);
      }
      return response;
    } catch (err) {
      notifications.error("Failed to import data set.");
    }
  };

  public openTestflowDataSetTab = async (dataSet: TestflowDataSetItem) => {
    const progressiveTab = createDeepCopy(this._tab.getValue());
    const initTestflowDataSetTab = new InitTestflowDataSetTab(
      dataSet.id,
      progressiveTab.path.workspaceId,
      progressiveTab.id,
    )
      .updateName(dataSet.name)
      .updateDataSet(dataSet?.item)
      .updateFormatType(dataSet.formatType)
      .updateTimestamps(dataSet.createdAt, dataSet.updatedAt)
      .updateFileDetails({
        fileSize: dataSet.fileSize,
        fileUrl: dataSet.fileUrl,
        updatedBy: dataSet.updatedBy,
      })
      .updateUpdatedAt(dataSet?.updatedAt || "")
      .getValue();
    await this.tabRepository.removeTab(initTestflowDataSetTab.id);
    await this.tabRepository.createTab(initTestflowDataSetTab);
  };
}
